<?php

declare(strict_types=1);

namespace Components\Utils\Funcs;

use ReflectionClass;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionProperty;
use ReflectionType;
use ReflectionUnionType;
use Throwable;

/**
 * 对象操作辅助函数
 * @author vijay
 */
class BeanUtils
{
    /**
     * 将源对象的属性赋值给目标对象的同名属性
     * @param object $source 源对象
     * @param object $target 目标对象
     * @param string ...$ignoreProperties 要忽略的属性列表
     * @return T 目标对象
     * @psalm-template T
     * @psalm-param T $target
     */
    public static function copyProperties($source, $target, string ...$ignoreProperties)
    {
        return self::copyPropertiesArray($source, $target, $ignoreProperties);
    }

    /**
     * 将源对象的属性赋值给目标对象的同名属性
     * @param object $source 源对象
     * @param object $target 目标对象
     * @param string[] $ignoreProperties 要忽略的属性列表
     * @return object
     * @note 这个先做一版，后面在使用的过程中逐步完善
     * @psalm-template T
     * @psalm-param T $target
     */
    public static function copyPropertiesArray($source, $target, array $ignoreProperties)
    {
        $sourceReflection = new ReflectionClass($source);
        /** @noinspection PhpUnhandledExceptionInspection */
        $targetReflection = new ReflectionClass($target);
        $ignoreProperties = array_flip($ignoreProperties);
        do {
            foreach ($sourceReflection->getProperties() as $sourceProperty) {
                $need = self::needCopy($ignoreProperties, $sourceProperty, $source);
                $ignoreProperties[$sourceProperty->getName()] = true;
                if (!$need) {
                    continue;
                }
                $targetProperty = self::getTargetProperty($targetReflection, $sourceProperty->getName());
                if (!$targetProperty) {
                    continue;
                }
                self::setValue(
                    $target,
                    $targetReflection,
                    $targetProperty,
                    self::getValue($source, $sourceReflection, $sourceProperty)
                );
            }
            $sourceReflection = $sourceReflection->getParentClass();
        } while ($sourceReflection);
        foreach (get_object_vars($source) as $field => $sourceValue) {
            if (isset($ignoreProperties[$field])) {
                continue;
            }
            $targetProperty = self::getTargetProperty($targetReflection, $field);
            if (!$targetProperty) {
                continue;
            }
            self::setValue(
                $target,
                $targetReflection,
                $targetProperty,
                $sourceValue
            );
        }
        return $target;
    }

    /**
     * 判断属性是否需要复制
     * @param array $ignoreProperties key是忽略的属性列表
     * @param ReflectionProperty $sourceProperty 源对象属性反射
     * @param object $source 源对象
     * @return bool
     */
    private static function needCopy(
        array $ignoreProperties,
        ReflectionProperty $sourceProperty,
        $source
    ): bool {
        if ($sourceProperty->isStatic()) {
            # 不复制静态属性
            return false;
        }
        if (isset($ignoreProperties[$sourceProperty->getName()])) {
            # 忽略的属性不需要
            return false;
        }
        if (!$sourceProperty->isInitialized($source)) {
            # 未初始化的字段不复制
            return false;
        }
        return true;
    }

    /**
     * 获取目标属性
     * @param ReflectionClass $targetReflection 目标对象反射
     * @param string $propertyName 属性名
     * @return ReflectionProperty|null
     */
    private static function getTargetProperty(
        ReflectionClass $targetReflection,
        string $propertyName
    ): ?ReflectionProperty {
        $targetProperty = self::getPropertyByName($targetReflection, $propertyName);
        if (!$targetProperty) {
            return null;
        }
        if ($targetProperty->isStatic()) {
            # 不复制静态属性
            return null;
        }
        if ($targetProperty->isReadOnly()) {
            # 只读属性无法写入
            return null;
        }
        return $targetProperty;
    }

    /**
     * 通性属性名称获取属性
     * @param ReflectionClass $reflection 类反射
     * @param string $propertyName 要获取的属性名
     * @return ReflectionProperty|null
     */
    private static function getPropertyByName(ReflectionClass $reflection, string $propertyName): ?ReflectionProperty
    {
        do {
            if ($reflection->hasProperty($propertyName)) {
                return $reflection->getProperty($propertyName);
            }
            $reflection = $reflection->getParentClass();
        } while ($reflection);
        return null;
    }

    /**
     * 获取值
     * @param object $source 源对象
     * @param ReflectionClass $sourceReflection 源类反射
     * @param ReflectionProperty $sourceProperty 源属性反射
     * @return mixed 字段的值
     */
    private static function getValue(
        $source,
        ReflectionClass $sourceReflection,
        ReflectionProperty $sourceProperty
    ) {
        $name = ucfirst($sourceProperty->getName());
        # 优先取get方法
        $methodName = "get{$name}";
        if (self::hasPublicMethod($sourceReflection, $methodName, 0)) {
            return call_user_func(
                [
                    $source,
                    $methodName,
                ]
            );
        }
        $methodName = "is{$name}";
        if (self::hasPublicMethod($sourceReflection, $methodName, 0)) {
            return call_user_func(
                [
                    $source,
                    $methodName,
                ]
            );
        }
        return $sourceProperty->getValue($source);
    }

    /**
     * 设置值
     * @param object $target 目标对象
     * @param ReflectionClass $targetReflection 目标对象反射
     * @param ReflectionProperty $targetProperty 目标属性反射
     * @param mixed $value 要设置的值
     * @return void
     */
    private static function setValue(
        $target,
        ReflectionClass $targetReflection,
        ReflectionProperty $targetProperty,
        $value
    ): void {
        $name = ucfirst($targetProperty->getName());
        # 优先取set方法
        $methodName = "set{$name}";
        $method = self::hasPublicMethod($targetReflection, $methodName, 1);
        if ($method) {
            $type = $method->getParameters()[0]->getType();
        } else {
            $type = $targetProperty->getType();
        }
        if (!self::validType($type, $value)) {
            # 类型不匹配，不复制
            return;
        }
        try {
            if ($method) {
                call_user_func(
                    [
                        $target,
                        $methodName,
                    ],
                    $value
                );
            } else {
                $targetProperty->setValue($target, $value);
            }
            // @codeCoverageIgnoreStart
        } catch (Throwable $throwable) {
        }
        // @codeCoverageIgnoreEnd
    }

    /**
     * 源值与目标类型是否匹配
     * @param ReflectionType|null $type 目标属性的类型描述
     * @param mixed $sourceValue 源值
     * @return bool 是否匹配
     */
    private static function validType(?ReflectionType $type, $sourceValue): bool
    {
        if (!$type) {
            return true;
        }
        if (is_null($sourceValue)) {
            return $type->allowsNull();
        }
        switch (get_class($type)) {
            case ReflectionUnionType::class:
                foreach ($type->getTypes() as $t) {
                    if (self::validType($t, $sourceValue)) {
                        return true;
                    }
                }
                return false;
            case ReflectionIntersectionType::class:
                foreach ($type->getTypes() as $t) {
                    if (!self::validType($t, $sourceValue)) {
                        return false;
                    }
                }
                return true;
            case ReflectionNamedType::class:
                return self::validNameType($type, $sourceValue);
            default:
                // @codeCoverageIgnoreStart
                return false;
        }
        // @codeCoverageIgnoreEnd
    }

    /**
     * 源值与目标类型是否匹配
     * @param ReflectionNamedType $type 目标属性的类型描述
     * @param mixed $sourceValue 源值
     * @return bool 是否匹配
     */
    private static function validNameType(ReflectionNamedType $type, $sourceValue): bool
    {
        $typeName = $type->getName();
        switch ($typeName) {
            case"array":
                return is_array($sourceValue);
            case "string" :
                return is_string($sourceValue) || is_numeric($sourceValue) || is_bool($sourceValue);
            case "bool" :
                return is_bool($sourceValue) || is_numeric($sourceValue) || is_string($sourceValue);
            case "int" :
                return is_int($sourceValue);
            case"float" :
                return is_numeric($sourceValue);
            case "callable" :
                return is_callable($sourceValue);
            case "mixed" :
                return true;
            default:
                return (class_exists($typeName) || interface_exists($typeName)) && $sourceValue instanceof $typeName;
        }
    }

    /**
     * 是否有有效的公共方法
     * @param ReflectionClass $reflectionClass 类反射
     * @param string $methodName 方法名
     * @param int $needParamCount 需要的最小参数数量
     * @return ReflectionMethod|bool 存在返回方法反射，否则返回false
     */
    private static function hasPublicMethod(
        ReflectionClass $reflectionClass,
        string $methodName,
        int $needParamCount
    ) {
        if (!$reflectionClass->hasMethod($methodName)) {
            return false;
        }
        $method = $reflectionClass->getMethod($methodName);
        if ($method->getNumberOfRequiredParameters() > $needParamCount) {
            # 要的参数过多
            return false;
        }
        if ($method->isPublic() && !$method->isStatic()) {
            return $method;
        } else {
            return false;
        }
    }
}

# end of file
